#include "config.h"

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/tcp.h>
#include <netinet/in.h>
#include <linux/sockios.h>
#include <linux/ethtool.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <netdb.h>
#include <fcntl.h>
#include <errno.h>

#include "libnetlink.h"
#include "sysy_lib.h"
#include "net_tool.h"
#include "dbg.h"

static int ipaddr_modify(int cmd, int flags, const char *ip, const char *if_name, struct rtnl_handle *rth)
{
        inet_prefix lcl;
        // structure of the netlink packet.
        struct {
                struct nlmsghdr     n;
                struct ifaddrmsg    ifa;
                char            buf[1024];
        } req;

        memset(&req, 0, sizeof(req));
        req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg));
        req.n.nlmsg_type = cmd;
        req.n.nlmsg_flags = NLM_F_REQUEST | flags;

        req.ifa.ifa_family = AF_INET ;
        req.ifa.ifa_index = if_nametoindex(if_name);
        req.ifa.ifa_scope = 0 ;

        get_prefix(&lcl, ip, req.ifa.ifa_family);
        if (req.ifa.ifa_family == AF_UNSPEC)
                req.ifa.ifa_family = lcl.family;
        req.ifa.ifa_prefixlen = lcl.bitlen;

        addattr_l(&req.n, sizeof(req), IFA_LOCAL, &lcl.data, lcl.bytelen);

        if (rtnl_talk(rth, &req.n, 0, 0, NULL) < 0)
                return -2;

        return 0;
}

int nettool_add_ip(const char *ip, const char *if_name)
{
        int ret;
        struct rtnl_handle rth;

        ret = rtnl_open(&rth);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = ipaddr_modify(RTM_NEWADDR, NLM_F_CREATE | NLM_F_EXCL, ip, if_name, &rth);
        if (unlikely(ret))
                GOTO(err_close, ret);

        rtnl_close(&rth);

        return 0;
err_close:
        rtnl_close(&rth);
err_ret:
        return ret;
}

int nettool_del_ip(const char *ip, const char *if_name)
{
        int ret;
        struct rtnl_handle rth;

        ret = rtnl_open(&rth);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = ipaddr_modify(RTM_DELADDR, 0, ip, if_name, &rth);
        if (unlikely(ret))
                GOTO(err_close, ret);

        rtnl_close(&rth);

        return 0;
err_close:
        rtnl_close(&rth);
err_ret:
        return ret;
}

int nettool_set_ip(const char *ip, const char *if_name)
{
        int ret, fd, mask_int;
        char _ip[MAX_NAME_LEN], *_mask;
        uint32_t mask;
        struct ifreq *req;
        struct sockaddr_in *addr;
        char buf[MAX_BUF_LEN];

        strcpy(_ip, ip);
        _mask = strchr(_ip, '/');
        *_mask = '\0';
        _mask++;

        if (nettool_is_use(_ip)) {
                ret = EADDRINUSE;
                GOTO(err_ret, ret);
        }

        mask_int = atoi(_mask);
        mask = nettool_netmask(mask_int);

        req = (void *)buf;
        strcpy(req->ifr_name, if_name);
        fd = socket(AF_INET, SOCK_STREAM, 0);
        if (fd < 0) {
                ret = errno;
                GOTO(err_ret, ret);
        }

        addr = (struct sockaddr_in *)&(req->ifr_addr);
        addr->sin_family = AF_INET;
        addr->sin_addr.s_addr = inet_addr(_ip);

        DINFO("if %s set ip %s\n", if_name, inet_ntoa(addr->sin_addr));

        ret = ioctl(fd, SIOCSIFADDR, req);
        if (ret < 0){
                ret = errno;
                GOTO(err_close, ret);
        }

        addr = (struct sockaddr_in *)&(req->ifr_addr);
        addr->sin_family = AF_INET;
        addr->sin_addr.s_addr = mask;

        DINFO("if %s set netmask %s\n", if_name, inet_ntoa(addr->sin_addr));

        ret = ioctl(fd, SIOCSIFNETMASK, req);
        if (ret < 0){
                ret = errno;
                GOTO(err_close, ret);
        }

        close(fd);

        return 0;
err_close:
        close(fd);
err_ret:
        return ret;
}

int nettool_set_down(const char *if_name)
{
        int ret, fd;
        struct ifreq req;

        if (!nettool_is_if(if_name)) {
                ret = EINVAL;
                GOTO(err_ret, ret);
        }

        strcpy(req.ifr_name, if_name);
        fd = socket(AF_INET, SOCK_STREAM, 0);
        if (fd < 0) {
                ret = errno;
                GOTO(err_ret, ret);
        }

        ret = ioctl(fd, SIOCGIFFLAGS, &req);
        if (ret < 0){
                ret = errno;
                GOTO(err_close, ret);
        }

        req.ifr_flags &= ~IFF_UP;

        ret = ioctl(fd, SIOCSIFFLAGS, &req);
        if (ret < 0){
                ret = errno;
                GOTO(err_close, ret);
        }

        close(fd);

        return 0;
err_close:
        close(fd);
err_ret:
        return ret;
}

int nettool_get_ip_byif(char *ip, char *mask, const char *if_name)
{
        int ret, fd;
        struct ifreq *req;
        struct sockaddr_in *addr;
        char buf[MAX_BUF_LEN];

        YASSERT(ip);
        YASSERT(mask);

        if (!nettool_is_if(if_name)) {
                ret = EINVAL;
                GOTO(err_ret, ret);
        }

        req = (void *)buf;
        strcpy(req->ifr_name, if_name);
        fd = socket(AF_INET, SOCK_STREAM, 0);
        if (fd < 0) {
                ret = errno;
                GOTO(err_ret, ret);
        }

        ret = ioctl(fd, SIOCGIFADDR, &req);
        if (ret < 0){
                ret = errno;
                GOTO(err_close, ret);
        }

        addr = (struct sockaddr_in *)&(req->ifr_addr);
        strcpy(ip, inet_ntoa(addr->sin_addr));

        ret = ioctl(fd, SIOCGIFNETMASK, &req);
        if (ret < 0){
                ret = errno;
                GOTO(err_close, ret);
        }

        addr = (struct sockaddr_in *)&(req->ifr_addr);
        strcpy(mask, inet_ntoa(addr->sin_addr));

        close(fd);

        return 0;
err_close:
        close(fd);
err_ret:
        return ret;
}

int nettool_get_ip_byvip(const uint32_t vip, char *ip, int *iplen)
{
        int ret, fd, if_len;
        struct ifconf ifc;
        struct ifreq buf[MAX_INFO_LEN];
        char *ip_addr;
        uint32_t if_ip, if_mask;
        struct sockaddr_in sockaddr;

        YASSERT(ip);

        fd = socket(AF_INET, SOCK_DGRAM, 0);
        if (fd < 0) {
                ret = errno;
                GOTO(err_ret, ret);
        }

        ifc.ifc_len = sizeof(buf);
        ifc.ifc_buf = (caddr_t) buf;

        ret = ioctl(fd, SIOCGIFCONF, (char *) &ifc);
        if (ret < 0){
                ret = errno;
                GOTO(err_close, ret);
        }

        *ip = '\0';
        *iplen = 1;

        if_len = ifc.ifc_len / sizeof(struct ifreq);
        while (if_len-- > 0) {
                ret = ioctl(fd, SIOCGIFFLAGS, (char *) &buf[if_len]);
                if (ret < 0){
                        ret = errno;
                        GOTO(err_close, ret);
                }

                if (!buf[if_len].ifr_flags & IFF_UP) {
                        continue;
                }

                ret = ioctl(fd, SIOCGIFADDR, (char *) &buf[if_len]);
                if (ret < 0){
                        ret = errno;
                        GOTO(err_close, ret);
                }

                if_ip = ((struct sockaddr_in*) (&buf[if_len].ifr_addr))->sin_addr.s_addr;
                sockaddr = *(struct sockaddr_in *)(&buf[if_len].ifr_addr);
                //ip_addr = (char*)inet_ntoa(((struct sockaddr_in*) (&buf[if_len].ifr_addr))->sin_addr);
                ip_addr = (char*)inet_ntoa(sockaddr.sin_addr);
                if (!strcmp("127.0.0.1", ip_addr)) {
                        continue;
                }

                ret = ioctl(fd, SIOCGIFNETMASK, (char *) &buf[if_len]);
                if (ret < 0){
                        ret = errno;
                        GOTO(err_close, ret);
                }
                if_mask = ((struct sockaddr_in*) (&buf[if_len].ifr_addr))->sin_addr.s_addr;

                if ((if_ip & if_mask) == (vip & if_mask) && (if_ip != vip)) {
                        strcpy(ip, ip_addr);
                        *iplen = strlen(ip_addr) + 1;
                        break;
                }
        }

        close(fd);

        return 0;
err_close:
        close(fd);
err_ret:
        return ret;
}

int nettool_get_if_bynet(char *if_name, const char *ip)
{
        int ret, fd, if_len, mask_int, found = 0;
        struct ifconf ifc;
        struct ifreq buf[MAX_INFO_LEN];
        char _ip[MAX_NAME_LEN], *_mask;
        struct in_addr addr;
        uint32_t if_addr, if_mask, cur_addr, cur_mask;

        YASSERT(if_name);

        strcpy(_ip, ip);
        _mask = strchr(_ip, '/');
        *_mask = '\0';
        _mask++;

        if (nettool_is_use(_ip)) {
                ret = EADDRINUSE;
                GOTO(err_ret, ret);
        }

        inet_aton(_ip, &addr);
        cur_addr = addr.s_addr;

        mask_int = atoi(_mask);
        cur_mask = nettool_netmask(mask_int);

        fd = socket(AF_INET, SOCK_DGRAM, 0);
        if (fd < 0) {
                ret = errno;
                GOTO(err_ret, ret);
        }

        ifc.ifc_len = sizeof(buf);
        ifc.ifc_buf = (caddr_t) buf;

        ret = ioctl(fd, SIOCGIFCONF, (char *) &ifc);
        if (ret < 0){
                ret = errno;
                GOTO(err_close, ret);
        }

        if_len = ifc.ifc_len / sizeof(struct ifreq);
        while (if_len-- > 0) {
                ret = ioctl(fd, SIOCGIFADDR, (char *) &buf[if_len]);
                if (ret < 0){
                        ret = errno;
                        GOTO(err_close, ret);
                }

                if_addr = ((struct sockaddr_in*) (&buf[if_len].ifr_addr))->sin_addr.s_addr;

                ret = ioctl(fd, SIOCGIFNETMASK, (char *) &buf[if_len]);
                if (ret < 0){
                        ret = errno;
                        GOTO(err_close, ret);
                }

                if_mask = ((struct sockaddr_in*) (&buf[if_len].ifr_addr))->sin_addr.s_addr;
                // in the same network segment
                if (((if_addr & if_mask) == (cur_addr & cur_mask)) && nettool_is_up(buf[if_len].ifr_name)){
                        found = 1;
                        strcpy(if_name, buf[if_len].ifr_name);
                }
        }

        if (!found) {
                ret = ENOENT;
                GOTO(err_close, ret);
        }

        close(fd);

        return 0;
err_close:
        close(fd);
err_ret:
        return ret;
}

int nettool_get_if_byip(char *if_name, const char *ip)
{
        int ret, fd, if_len, mask_int, found = 0;
        struct ifconf ifc;
        struct ifreq buf[MAX_INFO_LEN];
        char _ip[MAX_NAME_LEN], *_mask;
        struct in_addr addr;
        uint32_t if_addr, if_mask, cur_addr, cur_mask;
        struct sockaddr_in sockaddr;

        YASSERT(if_name);

        strcpy(_ip, ip);
        _mask = strchr(_ip, '/');
        *_mask = '\0';
        _mask++;

        inet_aton(_ip, &addr);
        cur_addr = addr.s_addr;

        mask_int = atoi(_mask);
        cur_mask = nettool_netmask(mask_int);

        fd = socket(AF_INET, SOCK_DGRAM, 0);
        if (fd < 0) {
                ret = errno;
                GOTO(err_ret, ret);
        }

        ifc.ifc_len = sizeof(buf);
        ifc.ifc_buf = (caddr_t) buf;

        ret = ioctl(fd, SIOCGIFCONF, (char *) &ifc);
        if (ret < 0){
                ret = errno;
                GOTO(err_close, ret);
        }

        if_len = ifc.ifc_len / sizeof(struct ifreq);
        while (if_len-- > 0) {
                ret = ioctl(fd, SIOCGIFADDR, (char *) &buf[if_len]);
                if (ret < 0){
                        ret = errno;
                        GOTO(err_close, ret);
                }

                sockaddr = *(struct sockaddr_in *)(&buf[if_len].ifr_addr);
                if (!strcmp(_ip, inet_ntoa(sockaddr.sin_addr))) {
                        if_addr = ((struct sockaddr_in*) (&buf[if_len].ifr_addr))->sin_addr.s_addr;

                        ret = ioctl(fd, SIOCGIFNETMASK, (char *) &buf[if_len]);
                        if (ret < 0){
                                ret = errno;
                                GOTO(err_close, ret);
                        }

                        if_mask = ((struct sockaddr_in*) (&buf[if_len].ifr_addr))->sin_addr.s_addr;
                        if ((if_addr & if_mask) == (cur_addr & cur_mask)){
                                found = 1;
                                strcpy(if_name, buf[if_len].ifr_name);
                        }
                }
        }

        if (!found) {
                ret = ENOENT;
                GOTO(err_close, ret);
        }

        close(fd);

        return 0;
err_close:
        close(fd);
err_ret:
        return ret;
}

int nettool_get_subif(char *sub_if_name, const char *if_name)
{
        int ret, i = 0;
        char _sub_if_name[MAX_NAME_LEN];

        YASSERT(sub_if_name);

        if (!nettool_is_if(if_name)) {
                ret = EINVAL;
                GOTO(err_ret, ret);
        }

        while(1) {
                sprintf(_sub_if_name, "%s:%d", if_name, i++);
                if (!nettool_is_if(_sub_if_name)) {
                        strcpy(sub_if_name, _sub_if_name);
                        break;
                }
        }

        return 0;
err_ret:
        return ret;
}

int nettool_get_mac(char *mac, const char *if_name)
{
        int ret, fd;
        struct ifreq req;

        YASSERT(mac);

        if (!nettool_is_if(if_name)) {
                ret = EINVAL;
                GOTO(err_ret, ret);
        }

        strcpy(req.ifr_name, if_name);
        fd = socket(AF_INET, SOCK_STREAM, 0);
        if (fd < 0) {
                ret = errno;
                GOTO(err_ret, ret);
        }

        ret = ioctl(fd, SIOCGIFHWADDR, &req);
        if (ret < 0){
                ret = errno;
                GOTO(err_close, ret);
        }

        sprintf(mac, "%02x:%02x:%02x:%02x:%02x:%02x",
                (unsigned char) req.ifr_hwaddr.sa_data[0],
                (unsigned char) req.ifr_hwaddr.sa_data[1],
                (unsigned char) req.ifr_hwaddr.sa_data[2],
                (unsigned char) req.ifr_hwaddr.sa_data[3],
                (unsigned char) req.ifr_hwaddr.sa_data[4],
                (unsigned char) req.ifr_hwaddr.sa_data[5]);

        close(fd);

        return 0;
err_close:
        close(fd);
err_ret:
        return ret;
}

int nettool_get_broadcast(char *brd, const char *if_name)
{
        int ret, fd;
        struct ifreq *req;
        struct sockaddr_in *addr;
        char buf[MAX_BUF_LEN];

        if (!brd || !nettool_is_if(if_name)) {
                ret = EINVAL;
                GOTO(err_ret, ret);
        }

        req = (void *)buf;
        strcpy(req->ifr_name, if_name);
        fd = socket(AF_INET, SOCK_STREAM, 0);
        if (fd < 0) {
                ret = errno;
                GOTO(err_ret, ret);
        }

        ret = ioctl(fd, SIOCGIFBRDADDR, &req);
        if (ret < 0){
                ret = errno;
                GOTO(err_close, ret);
        }

        addr = (struct sockaddr_in *)&(req->ifr_addr);
        strcpy(brd, inet_ntoa(addr->sin_addr));

        close(fd);

        return 0;
err_close:
        close(fd);
err_ret:
        return ret;
}

int nettool_is_if(const char *if_name)
{
        int ret, fd, if_len;
        struct ifconf ifc;
        struct ifreq buf[MAX_INFO_LEN];

        fd = socket(AF_INET, SOCK_DGRAM, 0);
        if (fd < 0) {
                ret = errno;
                GOTO(err_ret, ret);
        }

        ifc.ifc_len = sizeof(buf);
        ifc.ifc_buf = (caddr_t) buf;

        ret = ioctl(fd, SIOCGIFCONF, (char *) &ifc);
        if (ret < 0){
                ret = errno;
                GOTO(err_close, ret);
        }

        if_len = ifc.ifc_len / sizeof(struct ifreq);
        while (if_len-- > 0) {
                if (!strcmp(if_name, buf[if_len].ifr_name)) {
                        close(fd);
                        return 1;
                }
        }

err_close:
        close(fd);

err_ret:
        return 0;
}

int nettool_is_equality(const char *ip, struct sockaddr_in *addr)
{
        char _ip[MAX_NAME_LEN], *_mask;
        struct in_addr _addr;

        strcpy(_ip, ip);
        _mask = strchr(_ip, '/');
        *_mask = '\0';
        _mask++;

        inet_aton(_ip, &_addr);

        return _addr.s_addr == addr->sin_addr.s_addr;
}

int nettool_in_ipnet(const char *ip, struct sockaddr_in *addr)
{
        int mask_int;
        char _ip[MAX_NAME_LEN], *_mask;
        struct in_addr _addr;
        uint32_t mask;

        strcpy(_ip, ip);
        _mask = strchr(_ip, '/');
        if (NULL == _mask) {
                DERROR("ip (%s) is not a valid ip address", ip);
                return 0;
        }
        *_mask = '\0';
        _mask++;

        mask_int = atoi(_mask);
        mask = nettool_netmask(mask_int);

        inet_aton(_ip, &_addr);

        if ((_addr.s_addr & mask) == (addr->sin_addr.s_addr & mask))
                return 1;

        return 0;
}

int nettool_is_up(const char *if_name)
{
        int ret, fd;
        struct ifreq req;

        if (!nettool_is_if(if_name)) {
                ret = EINVAL;
                GOTO(err_ret, ret);
        }

        strcpy(req.ifr_name, if_name);
        fd = socket(AF_INET, SOCK_STREAM, 0);
        if (fd < 0) {
                ret = errno;
                GOTO(err_ret, ret);
        }

        ret = ioctl(fd, SIOCGIFFLAGS, &req);
        if (ret < 0){
                ret = errno;
                GOTO(err_close, ret);
        }

        if (req.ifr_flags & IFF_UP) {
                close(fd);
                return 1;
        }

err_close:
        close(fd);

err_ret:
        return 0;
}

#if 1
int nettool_is_ip(const char *_ip)
{
        int af;
        char addr[256] = {0}, ip[64] = {0}, *p;
        int ret;

        p = strchr(_ip, '/');
        if (p) {
               _memcpy(ip, _ip, p - _ip);
        } else {
               strcpy(ip, _ip);
        }

        if (strchr(ip, '.')) {
               af = AF_INET;
        } else if (strchr(ip, ':')) {
               af = AF_INET6;
        } else
               return 0;

        ret = inet_pton(af, ip, addr);
        if (ret <= 0) {
                return 0;
        }

        return 1;
}
#else
int nettool_is_ip(const char *ip)
{
        int has_mask = 0, section = 0, dot = 0, last = -1;

        YASSERT(ip);

        while(*ip) {
                if(*ip == '.') {
                        dot++;
                        if(dot > 3)
                                return 0;

                        if(section >= 0 && section <=255)
                                section = 0;
                        else
                                return 0;
                } else if (*ip == '/') {
                        has_mask = 1;
                        if(dot > 3)
                                return 0;

                        if(section >= 0 && section <=255)
                                section = 0;
                        else
                                return 0;
                } else if (*ip >= '0' && *ip <= '9') {
                        section = section * 10 + *ip - '0';

                        if(section < 10 && last == '0')
                                return 0;
                } else {
                        return 0;
                }

                last = *ip;
                ip++;
        }

        if (has_mask) {
                if(section > 0 && section < 32 && dot == 3)
                        return 1;

        }/*else if(section >= 0 && section <= 255 && dot == 3) {
                return 1;
        }
        */

        return 0;
}
#endif

int nettool_is_use(const char *ip)
{
        int ret, fd, if_len;
        struct ifconf ifc;
        struct ifreq buf[MAX_INFO_LEN];
        char _ip[MAX_NAME_LEN], *_mask;
        struct sockaddr_in sockaddr;

        strcpy(_ip, ip);
        _mask = strchr(_ip, '/');
        if (_mask) {
                *_mask = '\0';
                _mask++;
        }

        fd = socket(AF_INET, SOCK_DGRAM, 0);
        if (fd < 0) {
                ret = errno;
                GOTO(err_ret, ret);
        }

        ifc.ifc_len = sizeof(buf);
        ifc.ifc_buf = (caddr_t) buf;

        ret = ioctl(fd, SIOCGIFCONF, (char *) &ifc);
        if (ret < 0){
                ret = errno;
                GOTO(err_close, ret);
        }

        if_len = ifc.ifc_len / sizeof(struct ifreq);
        while (if_len-- > 0) {
                ret = ioctl(fd, SIOCGIFADDR, (char *) &buf[if_len]);
                if (ret < 0){
                        ret = errno;
                        GOTO(err_close, ret);
                }

                sockaddr = *(struct sockaddr_in*) (&buf[if_len].ifr_addr);
                if (!strcmp(_ip, (char *)inet_ntoa(sockaddr.sin_addr))) {
                        close(fd);
                        return 1;
                }
        }

err_close:
        close(fd);
err_ret:
        return 0;
}
